/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;
using System.Threading;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{
    [TestFixture(Description = "")]
    public class C954A02 : ValidationTest
    {

        //      Declare a protected type which simulates a printer device driver
        //      (foundation code).
        //
        //      Declare a task which simulates a printer server for multiple printers.
        //
        //      For the protected type, declare an entry with a barrier that is set
        //      false by a protected procedure (which simulates starting a print job
        //      on the printer), and is set true by a second protected procedure (which
        //      simulates a handler called when the printer interrupts, indicating
        //      that printing is done).
        //
        //      For the task, declare an entry whose corresponding accept statement
        //      contains a call to first protected procedure of the protected type
        //      (which sets the barrier of the protected entry to false), followed by
        //      a requeue with abort to the protected entry. Declare a second entry
        //      which does nothing.
        //
        //      Declare a "requesting" task which calls the printer server task entry
        //      (and thus executes the requeue). Attempt to abort the requesting
        //      task. Verify that it is aborted, that the requeued entry call is
        //      canceled, and that the corresponding entry body is not executed.


        const double LongEnough = Impdef.SwitchToNewTask;


        private static PrinterServer printServer;


        private class PrinterServer : ValidationTestTask
        {

            // Simulate a system with multiple printers. The entry Print requests
            // that data be printed on the next available printer. The entry call
            // is accepted when a printer is available, and completes when printing
            // is done.


            [Entry]
            internal Action<string> Print { get; private set; } // Test the requeue statement.

            [Entry]
            internal Action VerifyResults { get; private set; } // Artifice for test purposes.


            internal PrinterServer(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                bool printersBusy = true;
                int index = 0;
                bool printAccepted = false;

                while (true)
                {

                    while (printersBusy)
                    {
                        printersBusy = false; // Exit loop if entry accepted

                        int index1 = index;
                        
                        // Accepted immed. when printer is available
                        Printer.Printers[index].DonePrinting.Select().
                        Else(() =>
                        {
                            // Entry not immed. accepted; keep looping.
                            index1 = (index1 + 1) % Printer.NumberOfPrinters;
                            printersBusy = true;
                        });

                        index = index1;

                        // Allow other task to get control
                        Ada.Delay(Impdef.MinimumTaskSwitch);
                    }

                    // Value of Index at loop exit indicate avail. printer

                    // Wait for a print request or terminate

                    int index2 = index;

                    Select(s => s.

                        Accept(Print, (fileName) =>
                        {
                            // Allow VerifyResults to be accepted
                            printAccepted = true;

                            // Begin printing on the available printer
                            Printer.Printers[index2].StartPrinting(fileName);

                            // Requeue caller so server task is free to accept other requests
                            RequeueWithAbort(Printer.Printers[index2].DonePrinting);

                        }).

                    Or.

                        //  Guard ensures that VerifyResults cannot be accepted
                        //  until after Print has been accepted. This avoids a
                        //  race condition in the main program.
                        When(() => printAccepted).
                            Accept(VerifyResults).      // Artifice for test purpose

                    Or.

                        Terminate

                    );

                    // Allow other tasks to get control
                    Ada.Delay(Impdef.MinimumTaskSwitch);
                }
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Exception raised in PrinterServer task");
                Console.WriteLine(ex);
                Result.TestFailed();
            }
        }


        private class PrintRequest : ValidationTestTask
        {

            // Send a print request

            private const string MyFile = "MYFILE.DAT";


            internal PrintRequest(IValidationTestResult result)
                : base(result)
            {
            }


            protected override void Body()
            {
                printServer.Print(MyFile);

                Console.WriteLine("Task continued execution following entry call");
                Result.TestFailed();
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Exception raised in PrintRequest task");
                Console.WriteLine(ex);
                Result.TestFailed();
            }
        }


        [Test(Description =
            "Check that if a task requeued without abort on a protected entry queue " +
            "is aborted, the abort is deferred until the entry call completes, " +
            "after which the task becomes completed.")]
        public void Test()
        {

            //  To pass this test, the following must be true:
            // 
            //     (A) The abort of Print_Request takes place immediately.
            //     (B) The Done_Printing entry call is canceled, and the corresponding
            //         entry body is not executed.
            // 
            //  Call the entry VerifyResults. The entry call will not be accepted
            //  until after PrintRequest has been requeued to DonePrinting.

            IValidationTestResult result = Mocks.DynamicMock<IValidationTestResult>();
            PrintRequest printRequest = null;

            Ada.Declare(() =>
            {
                Printer.Initialize();

                printServer = new PrinterServer(result);
                printRequest = new PrintRequest(result);

                // Setup text expectations

                Expect.Call(() => result.TestFailed()).Repeat.Never();
                Expect.Call(() => result.TaskFinished(printServer));
                Expect.Call(() => result.TaskAborted(printRequest));

                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                try
                {
                    // Accepted after PrintRequest is requeued to DonePrinting
                    printServer.VerifyResults();

                    // Verify that the Done_Printing entry call has not been completed.
                    if (Printer.Printers[0].IsDone())
                    {
                        Console.WriteLine("Target entry of requeue executed prematurely");
                        result.TestFailed();
                    }
                    else
                    {
                        // Simulate an application which needs access to the printer within
                        // a specified time, and which aborts the current printer job if time
                        // runs out.

                        // Wait for the printer to come free
                        Printer.Printers[0].DonePrinting.Select().
                        // Print job took too long
                        OrDelay(LongEnough, () =>
                        {
                            // Abort print job
                            printRequest.Abort();
                        });

                        // Abortion completion point: force abort to complete (if it's going to).
                        printServer.VerifyResults();

                        Ada.Delay(Impdef.SwitchToNewTask);

                        // Verify (A): that Print_Request has been aborted.
                        // Note: the test will hang if the task has not been aborted
                        while (!printRequest.Terminated)
                        {
                            Ada.Delay(Impdef.MinimumTaskSwitch);
                        }

                        // Verify (B): that the Done_Printing entry call was canceled, and
                        // the corresponding entry body was not executed.
                        //
                        // Set the barrier of the entry to true, then check that the entry
                        // body is not executed. If the entry call is NOT canceled, the
                        // entry body will execute when the barrier is set true.

                        // Simulate a printer interrupt, signaling that printing is done
                        Printer.Printers[0].HandleInterrupt();

                        if (Printer.Printers[0].IsDone())
                        {
                            Console.WriteLine("Entry call was not canceled");
                            result.TestFailed();
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("_main_ threw exception " + ex.GetType().Name);
                    Console.WriteLine(ex);
                    throw;
                }
            });
        }

    }

}
